package jmine.tec.scheduling.quartz;

import java.util.concurrent.Callable;

import jmine.tec.scheduling.dao.ScheduledTaskExecutionDAO;
import jmine.tec.scheduling.domain.ScheduledTask;
import jmine.tec.scheduling.domain.ScheduledTaskExecution;

import org.apache.commons.lang.StringUtils;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;

import bancosys.tec.exception.LocalizedMessageHolder;
import bancosys.tec.exception.informer.UserFriendlyExceptionInformer;
import bancosys.tec.rtm.exception.ExceptionRecord;
import bancosys.tec.rtm.impl.RtmController;
import bancosys.tec.utils.date.Clock;
import bancosys.tec.utils.date.Timestamp;

/**
 * Objeto responsável pela verificação da condição de execução da tarefa e pela
 * execução propriamente dita.
 * 
 * @author ricardo
 */
public class ScheduledJob extends QuartzJobBean {

	private RtmController rtmController;

	private Clock clock;

	private ScheduledTaskExecutionDAO dao;

	private ScheduledTask task;

	private UserFriendlyExceptionInformer informer;

	/**
	 * Construtor
	 */
	public ScheduledJob() {
	}

	/**
	 * Constructor
	 * 
	 * @param rtmController
	 *            rtmController
	 * @param clock
	 *            clock
	 * @param dao
	 *            dao
	 * @param task
	 *            task
	 * @param informer
	 *            informer
	 */
	public ScheduledJob(RtmController rtmController, Clock clock,
			ScheduledTaskExecutionDAO dao, ScheduledTask task,
			UserFriendlyExceptionInformer informer) {
		this.rtmController = rtmController;
		this.clock = clock;
		this.dao = dao;
		this.task = task;
		this.informer = informer;
	}

	/**
	 * Executa a ação agendada, caso a condição seja satisfeita.
	 * 
	 * @param context
	 *            o contexto do quartz
	 */
	@Override
	protected void executeInternal(JobExecutionContext context) {
		final Timestamp start = this.clock.currentTimestamp();
		QuartzScheduledJobExecutionContext ctx = new QuartzScheduledJobExecutionContext();
		try {
			this.rtmController.executeTransacted(this.executeScheduledJob(
					start, ctx));
		} catch (final Exception e) {
			try {
				final Timestamp end = this.clock.currentTimestamp();
				this.rtmController.executeTransacted(this.logExecutionFailed(
						start, end, ctx, e));
			} catch (Exception e1) {
				this.rtmController.monitor(e);
				this.rtmController.monitor(e1);
			}
		}
	}

	/**
	 * Creates a callable that executes the scheduled job
	 * 
	 * @param start
	 *            start
	 * @param context
	 *            context
	 * @return callable that executes the job
	 * @throws Exception
	 *             Exception
	 */
	protected Callable<Void> executeScheduledJob(final Timestamp start,
			final QuartzScheduledJobExecutionContext context) throws Exception {
		return new Callable<Void>() {
			public Void call() throws Exception {
				boolean shouldRun = ScheduledJob.this.task
						.isConditionSatisfied(context);
				if (shouldRun) {
					ScheduledJob.this.task.executeAction(context);
				}
				Timestamp end = ScheduledJob.this.clock.currentTimestamp();
				String trace = StringUtils
						.join(context.getExecutionLog(), "\n");
				if (shouldRun) {
					ScheduledJob.this.dao.save(ScheduledTaskExecution.executed(
							ScheduledJob.this.task, start, end, trace));
				} else {
					ScheduledJob.this.dao.save(ScheduledTaskExecution
							.notExecuted(ScheduledJob.this.task, start, end,
									trace));
				}
				return null;
			}
		};
	}

	/**
	 * Creates a callable that logs a failed execution
	 * 
	 * @param start
	 *            start
	 * @param end
	 *            end
	 * @param context
	 *            context
	 * @return callable that logs the job fail
	 * @param exception
	 *            exception
	 */
	protected Callable<Void> logExecutionFailed(final Timestamp start,
			final Timestamp end,
			final QuartzScheduledJobExecutionContext context,
			final Exception exception) {
		return new Callable<Void>() {
			public Void call() throws Exception {
				ExceptionRecord record = ScheduledJob.this.rtmController
						.persist(exception);
				for (LocalizedMessageHolder message : ScheduledJob.this.informer
						.getUserFriendlyMessages(exception)) {
					context.log(message);
				}
				String trace = StringUtils
						.join(context.getExecutionLog(), "\n");
				ScheduledJob.this.dao.save(ScheduledTaskExecution.fail(
						ScheduledJob.this.task, start, end, trace, record));
				return null;
			}
		};
	}

	/**
	 * @param rtmController
	 *            the rtmController to set
	 */
	public void setRtmController(RtmController rtmController) {
		this.rtmController = rtmController;
	}

	/**
	 * @param clock
	 *            the clock to set
	 */
	public void setClock(Clock clock) {
		this.clock = clock;
	}

	/**
	 * @param scheduledTaskExecutionDAO
	 *            the dao to set
	 */
	public void setDao(ScheduledTaskExecutionDAO scheduledTaskExecutionDAO) {
		this.dao = scheduledTaskExecutionDAO;
	}

	/**
	 * @param task
	 *            the task to set
	 */
	public void setTask(ScheduledTask task) {
		this.task = task;
	}

	/**
	 * @param informer
	 *            the informer to set
	 */
	public void setInformer(UserFriendlyExceptionInformer informer) {
		this.informer = informer;
	}

}
